package de.hauschild.gmltracer.tracer.impl;

import java.util.Collections;
import java.util.List;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.math.geometry.Vector3D;
import org.apache.commons.math.linear.MatrixUtils;
import org.apache.commons.math.linear.RealMatrix;
import org.apache.commons.math.linear.RealMatrixImpl;

import de.hauschild.gmltracer.tracer.Intersection;
import de.hauschild.gmltracer.tracer.Ray;
import de.hauschild.gmltracer.tracer.Shape;
import de.hauschild.gmltracer.tracer.SurfaceFunction;
import de.hauschild.gmltracer.tracer.SurfaceProperties;

/**
 * The base implementation of {@link Shape} providing basic functionality.
 * 
 * @author Klaus Hauschild
 * @since 1.0.0
 */
public abstract class AbstractShape implements Shape {

  private RealMatrix transformation = MatrixUtils.createRealIdentityMatrix(4);
  private final SurfaceFunction surfaceFunction;

  public AbstractShape(final SurfaceFunction theSurfaceFunction) {
    surfaceFunction = theSurfaceFunction;
  }

  @Override
  public boolean equals(final Object obj) {
    if (obj == this) {
      return true;
    }
    if (!(obj instanceof AbstractShape)) {
      return false;
    }
    final AbstractShape rhs = (AbstractShape) obj;
    return new EqualsBuilder().append(surfaceFunction, rhs.surfaceFunction).append(transformation, rhs.transformation)
        .isEquals();
  }

  /**
   * Gets the surface function.
   * 
   * @return the surface function
   */
  public SurfaceFunction getSurfaceFunction() {
    return surfaceFunction;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public SurfaceProperties getSurfaceProperties(final Vector3D theIntersection) {
    final Vector3D objectCoordinates = objectCoordinates(theIntersection);
    final SurfaceProperties surefaceProperties = getSurfaceFunction().apply((int) objectCoordinates.getX(),
        objectCoordinates.getY(), objectCoordinates.getZ());
    return surefaceProperties;
  }

  @Override
  public int hashCode() {
    return new HashCodeBuilder().append(surfaceFunction).append(transformation).toHashCode();
  }

  @Override
  public Intersection intersect(final Ray ray) {
    final List<Shape> emptyList = Collections.emptyList();
    return intersect(ray, emptyList);
  }

  @Override
  public Intersection intersect(final Ray ray, final List<Shape> shapesToIgnore) {
    if (shapesToIgnore != null && shapesToIgnore.contains(this)) {
      return null;
    }

    return intersectAfterIgnoration(ray, shapesToIgnore);
  }

  /**
   * Gets the object coordinates for the intersection point.
   * 
   * @param intersection
   *          the intersection
   * @return the object coordinates
   */
  public abstract Vector3D objectCoordinates(final Vector3D intersection);

  @Override
  public void rotateX(final double degrees) {
    final RealMatrix rotation = new RealMatrixImpl(new double[][] { { 1, 0, 0, 0 },
        { 0, Math.cos(degrees), -Math.sin(degrees), 0 }, { 0, Math.sin(degrees), Math.cos(degrees), 0 },
        { 0, 0, 0, 1 }, });
    transformBy(rotation);
  }

  @Override
  public void rotateY(final double degrees) {
    final RealMatrix rotation = new RealMatrixImpl(new double[][] { { Math.cos(degrees), Math.sin(degrees), 0, 0 },
        { 0, 1, 0, 0 }, { -Math.sin(degrees), Math.cos(degrees), 0, 0 }, { 0, 0, 0, 1 }, });
    transformBy(rotation);
  }

  @Override
  public void rotateZ(final double degrees) {
    final RealMatrix rotation = new RealMatrixImpl(new double[][] { { Math.cos(degrees), -Math.sin(degrees), 0, 0 },
        { Math.sin(degrees), Math.cos(degrees), 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 }, });
    transformBy(rotation);
  }

  @Override
  public void scale(final double x, final double y, final double z) {
    final RealMatrix scale = new RealMatrixImpl(new double[][] { { x, 0, 0, 0 }, { 0, y, 0, 0 }, { 0, 0, z, 0 },
        { 0, 0, 0, 1 }, });
    transformBy(scale);
  }

  @Override
  public void translate(final double x, final double y, final double z) {
    final RealMatrix translation = new RealMatrixImpl(new double[][] { { 1, 0, 0, x }, { 0, 1, 0, y }, { 0, 0, 1, z },
        { 0, 0, 0, 1 }, });
    transformBy(translation);
  }

  @Override
  public void uniformScale(final double scale) {
    scale(scale, scale, scale);
  }

  protected abstract Intersection intersectAfterIgnoration(final Ray ray, final List<Shape> shapesToIngore);

  protected Vector3D objectToWorld(final Vector3D point) {
    return RendererUtils.transform(transformation, point);
  }

  protected Vector3D worldToObject(final Vector3D point) {
    return RendererUtils.transform(transformation.inverse(), point);
  }

  private void transformBy(final RealMatrix matrix) {
    transformation = transformation.multiply(matrix);
  }
}
